﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using Utils.SoftReferences;
//using Microsoft.Xna.Framework.Graphics;
using System.Text.RegularExpressions;

namespace GameObjects
{
    /// <summary>
    /// GameState is a snapshot of the state of the Game at an instant in time. It's not "active" - it doesn't know how to, say, process a set of
    /// Orders or anything like that.
    /// </summary> 
    [Serializable]
    public class GameState
    {
        

        public MapView MapView;

        public int Turn;
        public string Name;
        public const int MaxUnitMoveLimit = 4;
        public int TurnsPerSeason = 4;
        public int NumberOfSeasons = 4;
        public int NumberOfClimateZones = 3;

        public MetaData.Season Season
        {
            get
            {
                var calc = Turn / Convert.ToSingle(TurnsPerSeason * NumberOfSeasons);
                if (calc < .25f)
                    return MetaData.Season.Winter;
                if (calc < .5f)
                    return MetaData.Season.Spring;
                return calc < .75f ? MetaData.Season.Summer : MetaData.Season.Autumn;
            }
        }

        public IEnumerable<Unit> ActiveUnits
        {
            get { return MapView.Units.Where(o => o.Alive); }
        }

        public MetaData.ClimateZone GetClimateZone(Hex hex)
        {
            var calc = hex.Y / Convert.ToSingle(MapView.Board.Height);
            if (calc < .33)
                return MetaData.ClimateZone.Southern;
            return calc < .66 ? MetaData.ClimateZone.Central : MetaData.ClimateZone.Northern;
        }

        public MetaData.Weather GetWeather(Hex hex)
        {
            var zoneFactor = (float)GetClimateZone(hex) / NumberOfClimateZones;
            var seasonFactor = 0f;
            switch (Season)
            {
                case MetaData.Season.Spring:
                case MetaData.Season.Autumn:
                    seasonFactor = .5f;
                    break;
                case MetaData.Season.Summer:
                    seasonFactor = .25f;
                    break;
                case MetaData.Season.Winter:
                    seasonFactor = 1f;
                    break;
            }
            var calc = zoneFactor*seasonFactor;
            if (calc > .5f)
                return MetaData.Weather.Cold;
            if (calc > .25f)
                return MetaData.Weather.Wet;
            
            return calc < .125f ? MetaData.Weather.Dry : MetaData.Weather.Fine;
        }

        public TerrainType GetTerrainType(Hex hex)
        {
            //switch (GetWeather(hex))
            //{
            //    case MetaData.Weather.Dry:
            //        if (hex.TerrainType.Code == 'G')
            //            return TerrainTypes.Single(o => o.Code == 'D')
            //}
            return null;
        }


        public static bool GameTurnTimeExpired(DateTime serverTime, DateTime turnEnds)
        {
            return serverTime - turnEnds > TimeSpan.Zero;
        }


        

        public bool AllOrdersIn
        {
            get { return MapView.Factions.Count(o => o.OrdersIn) == MapView.Factions.Count; } 
        }

        public string WaitingOn
        {
            get
            {
                var result = "";
                foreach (var f in MapView.Factions.Where(o => !o.OrdersIn))
                    result += f.Name + ", ";

                return result.TrimEnd().TrimEnd(',');
            }
        }

        public string CreateVectorsFiles()
        {
            var lines = "";

            for (var i = 0; i < MapView.Vectors.Count(); i++)
            {
                var vector = MapView.Vectors.ElementAt(i);

                lines += vector.Nodes[0].X + "_" + vector.Nodes[0].Y + "-" +
                    vector.Nodes[1].X + "_" + vector.Nodes[1].Y + ", Road, " +
                    vector.Seed + "\r\n";
            }
            //lines.Last() = lines.Last().TrimEnd(';');
            return lines;
        }


        public Vector GetVector(Hex origin, Hex destination)
        {
            var vector = MapView.Vectors.SingleOrDefault(o => o.Nodes[0].Id == origin.Id && o.Nodes[1].Id == destination.Id) ??
                         MapView.Vectors.SingleOrDefault(o => o.Nodes[1].Id == origin.Id && o.Nodes[0].Id == destination.Id);

            return vector;
        }

        public void ProgressTurn(int turn, List<UnitMoveOrder> orders)
        {
            if (Turn != turn)
                throw new Exception("Wrong game turn!");

            var random = new Random(Turn);

            for (var step = 1; step < MaxUnitMoveLimit + 1; step++)
            {
                foreach (var unitOrder in orders.Where(o => o.Hexes != null && o.Valid))
                {
                    var unit = unitOrder.Unit;

                    // Ensure that the unit has't moved into a conflicted hex, invalidating the order from then on.
                    if (IsDisputedHex(unit.Location) && unitOrder.Hexes[0] != unit.Location)
                        unitOrder.Valid = false;
                }

                foreach (var unitOrder in orders.Where(o => o.Hexes != null && o.Valid))
                {
                    var unit = unitOrder.Unit;

                    var unitStep = (int)Math.Floor((float)MaxUnitMoveLimit / UnitMoveLimit(unit));

                    // Account for road movement
                    if (unitOrder.Hexes.Count > UnitMoveLimit(unit) + 1)
                        unitStep = MaxUnitMoveLimit / (UnitMoveLimit(unit) + 1);

                    // Can move 
                    if (step % unitStep != 0)
                        continue;

                    var index = step / unitStep;
                    if (index > unitOrder.Hexes.Count - 1)
                        continue;

                    unit.Location = unitOrder.Hexes[index];

                    // Boats on coasts
                    if (unit is WaterborneUnit)
                    {
                        var waterborneUnit = (WaterborneUnit) unit;
                        waterborneUnit.Coastline = null;

                        if (!unit.Location.Coastal)
                            continue;
                    
                        var previousMove = unitOrder.Hexes[index - 1];
                        if (previousMove == null)
                            continue;

                        Coastline = new List<Hex>();
                        if (!previousMove.Coastal)
                            waterborneUnit.LastSeaHex = previousMove;
                        else
                        {
                            if (waterborneUnit.LastSeaHex == null)
                            {
                                //throw new Exception("Ships always need to have a last sea hex defined");
                                waterborneUnit.LastSeaHex = waterborneUnit.Location.AdjacentCells.First(o => HexTerrainType(o).MovementType == MetaData.MoveType.Water);
                            }
                            var potentialLastMove = waterborneUnit.LastSeaHex.AdjacentCells.FirstOrDefault(
                                o =>
                                unit.Location.AdjacentCells.Contains(o) &&
                                HexTerrainType(o).MovementType ==
                                MetaData.MoveType.Water);

                            waterborneUnit.LastSeaHex = potentialLastMove ?? waterborneUnit.LastSeaHex;
                        }

                        FindCoastline(waterborneUnit.Location, waterborneUnit.LastSeaHex);

                        waterborneUnit.Coastline = Coastline;
                    }
                }
            }

            // Resolve Conflicts
            var hexesInConflict = MapView.Board.Hexes.Where(o => IsDisputedHex(o));
            foreach (var hex in hexesInConflict)
            {
                var totalDamage = new List<Damage>();

                // Calculate damage
                foreach (var p in MapView.Factions)
                {
                    if (UnitsInHex(hex).Any(o => o.Owner == p))
                        totalDamage.Add(new Damage(p, UnitsInHex(hex).Where(o => o.Owner != p).Sum(o => UnitStrength(o))));
                }

                // Apply Damage and reduce morale
                foreach (var unit in UnitsInHex(hex))
                {
                    var totalDamageForUnits = totalDamage.Single(o => o.Player == unit.Owner).DamageValue * .5f;
                    var numberOfUnits = UnitsInHex(hex).Count(o => o.Owner == unit.Owner);

                    var totalDamageForUnit = totalDamageForUnits/numberOfUnits;

                    unit.Casulties += (int)(totalDamageForUnit / unit.Quality);

                    var maxDamage = totalDamage.Max(o => o.DamageValue);
                    if (totalDamage.Any(o => o.Player == unit.Owner && o.DamageValue == maxDamage))
                        unit.Morale -= .5f;
                    else
                        unit.Morale--;
                }
            }

            // Remove dead units and apply morale effects
            foreach (var unit in ActiveUnits)
            {
                // Cold weather reduces morale
                if (GetWeather(unit.Location) == MetaData.Weather.Cold)
                    if (unit.HinderedByColdWeather)
                        if (unit.Location.Structure == null || unit.Location.Structure.Owner != unit.Owner)
                            if (unit.PreferredTerrain == null || unit.PreferredTerrain != HexTerrainType(unit.Location))
                                unit.Morale -= .5f;

                unit.ApplyMoraleEffects();

            }

            //ActiveUnits.RemoveAll(o => !o.Alive);

            var newFactions = new List<Faction>();

            // Handle rebellions
            foreach (var hex in MapView.Board.Hexes.Where(o => UnitsInHex(o).Count() > 0))
            {
                foreach (var f in MapView.Factions)
                {
                    var colour = String.Format("{0:X6}", random.Next(0x1000000));

                    var newFaction = new Faction(MapView.Factions.Count, "Rebel Faction", colour, colour);
                    var factionsUnitsInHex = UnitsInHex(hex).Where(o => o.Owner == f);

                    if (factionsUnitsInHex.All(o => o.MoraleStatus == MetaData.MoraleStatus.Rebellious))
                    {
                        foreach (var rebellingUnit in factionsUnitsInHex)
                        {
                            rebellingUnit.Rebel(newFaction);
                        }
                    }
                    if (ActiveUnits.Any(o => o.Owner == newFaction))
                        newFactions.Add(newFaction);
                }
            }

            MapView.Factions.AddRange(newFactions);

            // Add some militia to a disputed hex
            foreach (var structure in MapView.Structures.Where(o => IsDisputedHex(o.Hex) && !FactionsAtStackLimit(o.Hex).Contains(o.Owner) && !UnitsInHex(o.Hex).Any(a => a.Name.Contains("Militia"))))
            {
                var unit = new Unit(MapView.SoftRefSet, "Militia", structure.Hex, MetaData.MoveType.Land, null, structure.Owner,
                                    0, .3f, 50, 3, 3, 0, null, true, MapView.TerrainTypes);
                MapView.Units.Add(unit);
            }

            GenerateObservedHexes();

            Turn++;
        }

        public void GenerateObservedHexes()
        {
            // Observed hexes FoW
            foreach (var faction in MapView.Factions)
            {
                faction.ObservedHexes = new List<Hex>();
                var hexesWhereFactionHasUnits = MapView.Board.Hexes.Where(o => UnitsInHex(o).Any(a => a.Owner == faction));

                foreach (var hex in hexesWhereFactionHasUnits)
                {
                    faction.ObservedHexes.Add(hex);
                    if (!UnitsInHex(hex).Any(o => o.Vision > 0)) 
                        continue;

                    faction.ObservedHexes.AddRange(hex.AdjacentCells);
                    
                    if (!UnitsInHex(hex).Any(o => o.Vision > 1)) 
                        continue;

                    foreach (var extra in hex.AdjacentCells)
                    {
                        if (HexTerrainType(extra).MovementCost != MetaData.MovementCost.Impassable)
                            faction.ObservedHexes.AddRange(extra.AdjacentCells);
                    }
                }
            }
        }

        private void GenerateUnitMoveList(int movesLeft, Move move, Unit unit, bool onRoads)
        {
            if (onRoads && unit.MoveType != MetaData.MoveType.Land)
                return;

            if (movesLeft < 1)
                return;

            var possibleHexes = from o in move.Destination.AdjacentCells
                                where
                                (unit.MoveType == MetaData.MoveType.Airborne ||
                                (unit.MoveType == MetaData.MoveType.Water && o.Coastal) ||
                                (HexTerrainType(o).MovementType == unit.MoveType)) && 
                                (!onRoads || GetVector(move.Destination, o) != null)
                                select o;

            foreach (var possibleHex in possibleHexes)
            {
                if (possibleHex == unit.Location)
                    continue;

                if (FactionsAtStackLimit(possibleHex).Contains(unit.Owner))
                    continue;

                // Impassable terrain
                if (!onRoads && unit.MovementTable.Single(o => o.TerrainType == HexTerrainType(possibleHex)).MovementCost == MetaData.MovementCost.Impassable)//    && HexTerrainType(possibleHex).MovementCost == MetaData.MovementCost.Impassable && unit.PreferredTerrain != HexTerrainType(possibleHex))
                    continue;

                // Cannot retreat to a disputed or enemy occupied hex.
                if (IsDisputedHex(unit.Location) && (IsDisputedHex(possibleHex) || EnemyOccupiedHex(possibleHex, unit.Owner)))
                    continue;

                // Boats on the coast
                if (Coastline != null && (unit.Location.AdjacentCells.Contains(possibleHex) && unit.Location.Coastal))
                {
                    if (!Coastline.Any(o => o == possibleHex))
                        continue;
                }

                // Fog of War
                if (!unit.Owner.DiscoveredHexes.Contains(possibleHex))
                    continue;

                var nextMove = new Move(possibleHex, move, MetaData.MovementCost.One);

                if (!unit.MoveAlreadyAdded(nextMove))
                    unit.MoveList.Add(nextMove);
            }

            var subList = unit.MoveList.Where(o => possibleHexes.Contains(o.Destination));
            for (var i = 0; i < subList.Count(); i++)
            {
                var movesRemaining = movesLeft;

                if (movesRemaining > 1)
                {
                    // Difficult terrain
                    if (!onRoads && unit.MovementTable.Single(o => o.TerrainType == HexTerrainType(subList.ElementAt(i).Destination)).MovementCost == MetaData.MovementCost.All)//   && unit.MoveType != MetaData.MoveType.Airborne)
                    {
                        movesRemaining = 1;
                        subList.ElementAt(i).Cost = MetaData.MovementCost.All;
                        //if (unit.PreferredTerrain == null)
                        //{
                        //    if (HexTerrainType(subList.ElementAt(i).Destination).MovementCost != MetaData.MovementCost.None)
                        //    {
                        //        movesRemaining = 1;
                        //        subList.ElementAt(i).Cost = MoveCost.All;
                        //    }
                        //}
                        //else if (unit.PreferredTerrain != HexTerrainType(subList.ElementAt(i).Destination) &&
                        //    HexTerrainType(subList.ElementAt(i).Destination).MovementCost != MetaData.MovementCost.None)
                        //{
                        //    movesRemaining = 1;
                        //    subList.ElementAt(i).Cost = MoveCost.All;
                        //}
                    }

                    // Aquatics on the coast
                    if (unit.MoveType == MetaData.MoveType.Water && HexTerrainType(subList.ElementAt(i).Destination).MovementType == MetaData.MoveType.Land)
                    {
                        movesRemaining = 1;
                        subList.ElementAt(i).Cost = MetaData.MovementCost.AllOnCoast;
                    }

                    // Can't move past disputed or enemy occupied hex
                    if (IsDisputedHex(subList.ElementAt(i).Destination) || EnemyOccupiedHex(subList.ElementAt(i).Destination, unit.Owner))
                    {
                        movesRemaining = 1;
                        subList.ElementAt(i).Cost = MetaData.MovementCost.All;
                    }
                }

                movesRemaining--;

                GenerateUnitMoveList(movesRemaining, subList.ElementAt(i), unit, onRoads);
            }
        }

        public IEnumerable<Move> GetMoveList(Unit unit)
        {
            unit.MoveList = new List<Move>();

            var normalMoveSteps = UnitMoveLimit(unit);

            if (GetWeather(unit.Location) != MetaData.Weather.Cold)
            {
                // Land units on roads with good weather
                var roadMoveSteps = IsDisputedHex(unit.Location) ? UnitMoveLimit(unit) : UnitMoveLimit(unit) + 1;
                GenerateUnitMoveList(roadMoveSteps, new Move(unit.Location, null, MetaData.MovementCost.One), unit, true);
            }

            // Normal movement
            GenerateUnitMoveList(normalMoveSteps, new Move(unit.Location, null, MetaData.MovementCost.One), unit, false);

            // Airborne units over mountains and water
            foreach (var move in unit.MoveList.Where(o => unit.MovementTable.Single(mt => mt.TerrainType == HexTerrainType(o.Destination)).MovementCost == MetaData.MovementCost.OneToFlyOver)) //HexTerrainType(o.Destination).MovementCost == MetaData.MovementCost.Impassable && 
                    move.Cost = MetaData.MovementCost.OneToFlyOver;

            return unit.MoveList;
        }

        public bool GameOver
        {
            get { return UnitWin || StructureWin ; }
        }

        private bool UnitWin
        {
            get { return ActiveUnits.GroupBy(o => o.Owner).Count() == 1; }
        }

        private bool StructureWin
        {
            get { return MapView.Structures.GroupBy(o => o.Owner).Count() == 1; }
        }

        public Faction Winner
        {
            get { return UnitWin ? ActiveUnits.First().Owner : StructureWin ? MapView.Structures.First().Owner : null; }
        }

        public bool AreAdjacentCells(Hex origin, Hex destination)
        {
            if (origin.X == destination.X && Math.Abs(origin.Y - destination.Y) == 1)
                return true;

            if (origin.X % 2 == 0 &&
                ((origin.X + 1 == destination.X && origin.Y == destination.Y) ||
                (origin.X + 1 == destination.X && origin.Y - 1 == destination.Y) ||
                (origin.X - 1 == destination.X && origin.Y == destination.Y) ||
                (origin.X - 1 == destination.X && origin.Y - 1 == destination.Y)))
                return true;

            if (origin.X % 2 != 0 &&
                ((origin.X + 1 == destination.X && origin.Y + 1 == destination.Y) ||
                (origin.X + 1 == destination.X && origin.Y == destination.Y) ||
                (origin.X - 1 == destination.X && origin.Y + 1 == destination.Y) ||
                (origin.X - 1 == destination.X && origin.Y == destination.Y)))
                return true;

            return false;
        }

        public List<Hex> Coastline;
        public void FindCoastline(Hex origin, Hex startHex)//, ref int currentCoastline)
        {
            if (Coastline.Contains(startHex)) return;

            Coastline.Add(startHex);

            if (HexTerrainType(startHex).MovementType == MetaData.MoveType.Land) return;

            var sharedNeighbours = startHex.AdjacentCells.Where(o => origin.AdjacentCells.Contains(o));
            foreach (var sharedNeighbour in sharedNeighbours)
            {
                FindCoastline(origin, sharedNeighbour);
            }
        }

        public Vector GetVector(Hex origin, Hex destination, IList<Vector> vectors)
        {
            var vector = vectors.SingleOrDefault(o => o.Nodes[0].Id == origin.Id && o.Nodes[1].Id == destination.Id) ??
                         vectors.SingleOrDefault(o => o.Nodes[1].Id == origin.Id && o.Nodes[0].Id == destination.Id);

            return vector;
        }

        public static TimeSpan ParseTurnTimeLimit(string turnTimeLimit)
        {
            TimeSpan timeLimit;
            try
            {
                timeLimit = TimeSpan.Parse(turnTimeLimit);
                if (timeLimit < new TimeSpan(0, 0, 30))
                    throw new ArgumentOutOfRangeException("turnTimeLimit", "Please enter a turn limit larger than or equal to thirty seconds");
            }
            catch (Exception ex)
            {
                if (ex is ArgumentOutOfRangeException)
                    throw;
                throw new Exception("Please enter a valid time limit with format of dd:hh:mm:ss");
            }
            
            return timeLimit;
        }

        public bool UnitVisible(Faction player, Unit unit)
        {
            if (!player.ObservedHexes.Contains(unit.Location))
                return false;

            return unit.Owner == player || UnitsInHex(unit.Location).Any(o => o.Owner == player) || unit.ConcealmentTerrain == null || unit.ConcealmentTerrain != HexTerrainType(unit.Location);
        }

        public float TerrainStrengthModifier(Unit unit)
        {
            return HexTerrainType(unit.Location) == unit.PreferredTerrain ? 2 : 1;
        }

        public string UnitStrengthCalculationWorking(Unit unit)
        {
            var working = "Quality " + unit.Quality + " * Quantity " + unit.Quantity;
            if (TerrainStrengthModifier(unit) != 1)
                working += " * Terrain Bonus " + TerrainStrengthModifier(unit);
            if (unit.StructureStrengthModifier != 1)
                working += " * Structure Bonus " + unit.StructureStrengthModifier;
            return working;
            
        }

        public float UnitStrength(Unit unit)
        {
            return unit.Quality * unit.Quantity * TerrainStrengthModifier(unit) * unit.StructureStrengthModifier;
        }

        public string UnitStrengthStatus(Unit unit)
        {
            if (UnitStrength(unit) / AverageUnitStrength > .9 && UnitStrength(unit) / AverageUnitStrength < 1.1)
                return "Average";

            if (UnitStrength(unit) / AverageUnitStrength >= 1.4)
                return "Elite";

            if (UnitStrength(unit) / AverageUnitStrength >= 1.2)
                return "Superb";

            if (UnitStrength(unit) / AverageUnitStrength >= 1.1)
                return "Above Average";

            if (UnitStrength(unit) / AverageUnitStrength <= .6)
                return "Abysmal";

            return UnitStrength(unit) / AverageUnitStrength <= .8 ? "Weak" : "Below Average";
        }

        public bool EnemyOccupiedHex(Hex hex, Faction faction)
        {
            return UnitsInHex(hex).Where(o => UnitVisible(faction, o)).GroupBy(o => o.Owner).Count() > 0;
        }

        private float AverageUnitStrength
        {
            get { return ActiveUnits.Average(o => UnitStrength(o)); }
        }

        public TerrainType HexTerrainType(Hex hex)
        {
            switch (GetWeather(hex))
            {
                case MetaData.Weather.Wet:
                    if (hex.TerrainType.Code == 'D')
                        return MapView.TerrainTypes.Single(o => o.Code == 'G');
                    if (hex.TerrainType.Code == 'G')
                        return MapView.TerrainTypes.Single(o => o.Code == 'W');
                    if (hex.TerrainType.Code == 'W')
                        return MapView.TerrainTypes.Single(o => o.Code == 'L');
                    break;
                case MetaData.Weather.Dry:
                    if (hex.TerrainType.Code == 'L')
                        return MapView.TerrainTypes.Single(o => o.Code == 'W');
                    if (hex.TerrainType.Code == 'W')
                        return MapView.TerrainTypes.Single(o => o.Code == 'G');
                    if (hex.TerrainType.Code == 'G')
                        return MapView.TerrainTypes.Single(o => o.Code == 'D');
                    break;
            }
            return hex.TerrainType;
        }

        public IList<Faction> FactionsAtStackLimit(Hex hex)
        {
       
            var playersWithBreachedLimit = new List<Faction>();
            foreach (var player in MapView.Factions)
            {
                var numberOfUnitsHereOrComing = MapView.Units.Count(o => o.Owner == player) +
                    MapView.Factions.Single(o => o == player).Orders.Count(o => o.Hexes.Last().X == hex.X && o.Hexes.Last().Y == hex.Y) -
                    MapView.Factions.Single(o => o == player).Orders.Count(o => o.Unit.Location == hex);
                if (numberOfUnitsHereOrComing >= hex.StackLimit) playersWithBreachedLimit.Add(player);
            }
            return playersWithBreachedLimit;
        }

        public IEnumerable<Unit> UnitsInHex(Hex hex)
        {
            return ActiveUnits.Where(o => o.Location.X == hex.X && o.Location.Y == hex.Y);
        }

        public bool IsDisputedHex(Hex hex)
        {
            return UnitsInHex(hex).GroupBy(o => o.Owner).Count() > 1;
        }

        public Faction ChangeStructureOwner(Structure structure)
        {
            if (structure.Owner != structure.PreviousOwner)
            {
                structure.PreviousOwner = structure.Owner;
                foreach (var unit in UnitsInHex(structure.Hex).Where(o => o.Owner == structure.Owner))
                    unit.Morale++;
            }

            return structure.Owner;
        }

        
        public int UnitMoveLimit(Unit unit)
        {
            
            if (IsDisputedHex(unit.Location))
                return unit.BaseMoveLimit > 0 ? 1 : 0;
            if ((int)unit.MoraleStatus < (int)MetaData.MoraleStatus.Stable)
                return unit.BaseMoveLimit > 0 ? unit.BaseMoveLimit - 1 : 0;
            return unit.BaseMoveLimit;
        }
    }



    class Damage
    {
        public Faction Player;
        public float DamageValue;

        public Damage(Faction p, float d)
        {
            Player = p;
            DamageValue = d;
        }
    }

}
